package com.zhao.framework;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
 

/**
 * 
 * 实现servlet的抽象类。作为所有http请求的入口�?
 * 负责把请求映射到具体的类实例和方法�?
 * 
 * 此处，我们要实现�?���?��的基于reflect的web框架，使得前端的调用跟后端的业务类的实现�?
 * �?��对应。譬如，javascritp: feedback.getFeedbackByCity(city,0,10);直接被映射到feedback.getFeedbackByCity方法�?
 * 从�?省去很多机械重复的负责http协议解释的代码�?
 * 
 * 实际http请求应该符合以下规范�?
 * 包括以下参数�?
 * 1、class=bcms.poi.feedback  全路径的类名
 * 2、method=getFeedbackByCity 方法�?
 * 3、arg0=xxx&arg1=xxx... 参数列表，由”arg“和表示参数次序的数字组成参数名。全部参数�?要做urlencode
 * 
 * 本类根据class获取工厂类，进�?获取类实例；并利用reflect�?��，调用对应的方法�?
 * 注：类方法不能重载�?因为是根据类名映射，我们无法得知参数的类型，�?��不能有重复的方法名�?
 * 服务器段示例请参见：EasywebServlet.java
 */
public abstract class AbstractServlet extends HttpServlet{
	protected static final Log log = LogFactory.getLog(AbstractServlet.class); 
	/**
	 * 请求超时阈�?，用于�?能监控和分析。debug模式下记录超时请求的详细信息供分析�?
	 */
	private int warnTimeOut = 500;
	/**
	 * 全路径类名到工程类实例的映射�?
	 */
	protected Map<String,IApiFactory>factorys=new Hashtable<String,IApiFactory>();
	
	/**
	 * 建议子类在setup中调用本方法添加业务类的工厂类�?
	 * @param path
	 * @param fctr
	 */
	protected void addFactory(String path,IApiFactory fctr){
		factorys.put(path, fctr);
	}
	/**
	 * 此处实现初始化（被GenericServlet.init调用）�?包括设置各IApiFactory的实现�?
	 * 譬如：addFactory(IGisConflict.class.getName(),factory);
	 */
	protected abstract void setup();
	/* (non-Javadoc)
	 * @see javax.servlet.GenericServlet#init()
	 */
	@Override
	public void init() throws ServletException {
		this.setup();
	}

	/**
	 * 映射在此实现。call by doPost
	 * @param req
	 * @param resp
	 * @throws ServletException
	 * @throws IOException
	 */
	protected void action(HttpServletRequest req,
			HttpServletResponse resp) throws ServletException, IOException{
		String cls = req.getParameter("class");
		
		Object api = null;
		api = req.getSession().getAttribute(cls);
		if(null == api){
			IApiFactory fctr = factorys.get(cls);
			if(null == fctr){
				resp.getWriter().write(Result.get(false, "Invalid parameter: cls. Can not find this class's Factory.", null));
				return;
			}
			api = fctr.getApiInstance(cls);
			if( api instanceof SessionHolder){
				req.getSession().setAttribute(cls, api);
				((SessionHolder)api).setSession(req.getSession());
			}
		}
		
		if(null == api){
			log.error("Server error. Can not get api instance from factory for class:<"+cls+">");
			resp.getWriter().write(Result.get(false, "Server error. Can not get api instance from factory for class:<"+cls+">", null));
			return;
		}
		//get Method
		Method method = getMethodByName(api.getClass(),req.getParameter("method"));
		if(null == method){
			resp.getWriter().write(Result.get(false, "Can not find method:<"+req.getParameter("method")+"> in class::<"+cls+">", null));
			return;
		}		
		//get Parameters
		List<Object>args = new ArrayList<Object>();
		int idx=0;
		try{
			for(Type type : method.getGenericParameterTypes()){
				args.add(to(req.getParameter("arg"+(idx++)),type));
			}
		}catch(Exception e){
			log.warn("arg+"+(idx-1)+" convert fail in method<"+method.getName()+">",e);
			resp.getWriter().write(Result.get(false, "arg+"+(idx-1)+" convert fail in method<"+method.getName()+">", null));
			return;
		}
		//call
		Object retObj = null;
		try {
				retObj = method.invoke(api, args.toArray());
		} catch (Exception e) {
			log.error("fail to invoke metho<"+method.getName()+"> with args<"+Json.from(args)+">",e);
			resp.getWriter().write(Result.get(false, "fail to invoke method<"+method.getName()+"> with args<"+Json.from(args)+">", null));
			return;
		}
		//resp.getWriter().write(Result.get(true, "success", retObj));
		resp.getWriter().write((String)retObj);
		
	}

	/* (non-Javadoc)
	 * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		doPost(req, resp);
	}

	/* (non-Javadoc)
	 * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 * 统一用utf-8编码，无缓存�?
	 * http 相关的全�?��置在此实现�?
	 */
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		resp.setContentType("text/plain; charset=UTF-8");
		resp.setHeader("Pragma", "no-cache");
		resp.addHeader("Cache-Control", "must-revalidate");
		resp.addHeader("Cache-Control", "no-cache");
		resp.addHeader("Cache-Control", "no-store");
		resp.setDateHeader("Expires", 0);
		long startTime = System.currentTimeMillis();
		action(req, resp);
		long endTime = System.currentTimeMillis();
		long useTime = endTime - startTime;
		if (useTime > warnTimeOut) {
			String strWarn = req.getQueryString();
			strWarn = "The request use too much time:" + useTime + "."
					+ strWarn;
			log.warn(strWarn);
		} else {
			if (log.isDebugEnabled()) {
				String strlog = req.getQueryString();
				strlog = "Use time:" + useTime + "." + strlog;
				log.debug(strlog);
			}
		}
	}
	/**
	 * 根据方法名查找方法实例，不�?虑参数�?call by action
	 * @param cls
	 * @param name
	 * @return
	 */
	static private Method getMethodByName(Class cls,String name){
		for( Method m :cls.getMethods()){
			if(m.getName().equals(name)){
				return m;
			}
		}
		return null;
	} 
	/**
	 * 将参数转换为指定类型的对象�?(全部参数要进行urldecode)
	 * @param value
	 * @param type
	 * @return
	 * @throws IOException 
	 * @throws JsonMappingException 
	 * @throws JsonParseException 
	 */
	@SuppressWarnings("unchecked")
	static private Object to(String value,Type type) throws JsonParseException, JsonMappingException, IOException{
		if("null".equals(value)){
			return null;
		}
		if (type.equals(String.class)) {
	      return URLDecoder.decode(value, "UTF-8");
	    } else if (type.equals(Boolean.class) || type.equals(Boolean.TYPE)) {
	      return Boolean.valueOf(value);
	    } else if (type.equals(Byte.class) || type.equals(Byte.TYPE)) {
	      return Byte.valueOf(value);
	    } else if (type.equals(Integer.class) || type.equals(Integer.TYPE)) {
	      return Integer.valueOf(value);
	    } else if (type.equals(Long.class) || type.equals(Long.TYPE)) {
	      return Long.valueOf(value);
	    } else if (type.equals(Double.class) || type.equals(Double.TYPE)) {
	      return Double.valueOf(value);
	    } else if (type.equals(Float.class) || type.equals(Float.TYPE)) {
	      return Float.valueOf(value);
	    } 
		if(type instanceof ParameterizedType){
			return Json.urlDecode(value, TypeRef.getTypeRef(type));
		}else{
			return Json.urlDecode(value, (Class)type);
		} 
	}
	public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		
//		Method m = getMethodByName(Result.class,"Test");
//		Result r = new Result();
//		List<String> a = new ArrayList<String>();
//		a.add("hello");
//		a.add("haha");
//		a = Json.to("[\"hello\",\"haha\"]",  m.getGenericParameterTypes()[0]);
//		List<String> a = Json.to("[\"hello\",\"haha\"]",  new TypeRef<Object>(m.getGenericParameterTypes()[0]));
//		List<String> a = Json.to("[\"hello\",\"haha\"]",  TypeRef.getTypeRef(m.getGenericParameterTypes()[0]));
//		m.invoke(r, new Object[]{a});
//		m.invoke(r, new Object[]{"a2",2});
	}
}
